package org.example.ai04.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 简历解析服务类
 * 实现InitializingBean接口，用于在Bean初始化后注册AI模型配置
 * 该服务负责从不同格式的简历文件中提取文本内容，然后通过AI大语言模型进行结构化解析
 * 支持PDF、Word(.docx)和文本文件格式，并提供多种AI模型选项
 */
@Service
public class ResumeParserService implements InitializingBean {

    // HTTP客户端，用于调用AI模型API并处理响应
    private final RestTemplate restTemplate = new RestTemplate();
    // JSON处理工具，用于解析AI模型返回的响应内容
    private final ObjectMapper objectMapper = new ObjectMapper();
    // 存储AI模型配置的映射表，使用ConcurrentHashMap保证线程安全
    private final Map<String, AIModelConfig> modelConfigs = new ConcurrentHashMap<>();

    // 从配置文件中读取DeepSeek API密钥
    @Value("${ai.deepseek.api.key}")
    private String deepseekApiKey;

    // 从配置文件中读取DeepSeek API URL
    @Value("${ai.deepseek.api.url}")
    private String deepseekApiUrl;

    // 从配置文件中读取DeepSeek模型名称
    @Value("${ai.deepseek.model}")
    private String deepseekModel;

    // 从配置文件中读取腾讯混元API密钥
    @Value("${ai.hunyuan-lite.api.key}")
    private String hunyuanLiteApiKey;

    // 从配置文件中读取腾讯混元API URL
    @Value("${ai.hunyuan-lite.api.url}")
    private String hunyuanLiteApiUrl;

    // 从配置文件中读取腾讯混元模型名称
    @Value("${ai.hunyuan-lite.model}")
    private String hunyuanLiteModel;

    /**
     * Bean初始化后执行的方法
     * 用于注册AI模型配置，只有配置了有效API密钥的模型才会被注册
     * 实现InitializingBean接口的回调方法
     */
    @Override
    public void afterPropertiesSet() {
        // 注册DeepSeek模型（如果配置了有效API密钥）
        if (deepseekApiKey != null && !deepseekApiKey.trim().isEmpty()) {
            modelConfigs.put("deepseek", new AIModelConfig(deepseekApiUrl, deepseekApiKey, deepseekModel));
        }
        
        // 注册腾讯混元免费版模型（如果配置了有效API密钥）
        if (hunyuanLiteApiKey != null && !hunyuanLiteApiKey.trim().isEmpty()) {
            modelConfigs.put("hunyuan-lite", new AIModelConfig(hunyuanLiteApiUrl, hunyuanLiteApiKey, hunyuanLiteModel));
        }
    }

    /**
     * 核心简历解析方法
     * @param file 上传的简历文件
     * @param aiModel 要使用的AI模型名称
     * @return 解析后的结构化简历数据
     * @throws IOException 文件处理异常
     */
    public Map<String, Object> parseResume(MultipartFile file, String aiModel) throws IOException {
        // 第一步：从不同格式的文件中提取文本内容
        String resumeText = extractTextFromFile(file);
        
        // 第二步：验证并获取AI模型配置
        AIModelConfig config = modelConfigs.get(aiModel);
        if (config == null) {
            // 如果指定的模型不存在，默认使用deepseek模型
            config = modelConfigs.get("deepseek");
        }
        
        // 第三步：检查API密钥是否有效
        if (isInvalidApiKey(config.getApiKey())) {
            throw new RuntimeException("API密钥无效或未配置");
        }
        
        // 第四步：构建提示词，指导AI如何解析简历并生成结构化数据
        String prompt = buildPrompt(resumeText);
        
        // 第五步：调用AI模型获取解析结果
        String response = callLLM(prompt, config);
        
        try {
            // 第六步：解析AI模型返回的响应，提取结构化数据
            return parseResponse(response);
        } catch (Exception e) {
            throw new RuntimeException("简历解析失败: " + e.getMessage(), e);
        }
    }

    /**
     * 从不同格式的文件中提取文本内容
     * 支持PDF、Word(.docx)、文本文件，不支持.doc格式
     * @param file 上传的文件对象
     * @return 提取的文本内容
     * @throws IOException 文件处理异常
     */
    private String extractTextFromFile(MultipartFile file) throws IOException {
        // 获取文件名和内容类型
        String fileName = file.getOriginalFilename();
        String contentType = file.getContentType();
        
        // 验证文件名有效性
        if (fileName == null || fileName.trim().isEmpty()) {
            throw new IOException("文件名无效");
        }
        
        // 转换文件名到小写，便于扩展名匹配
        String lowerFileName = fileName.toLowerCase();
        
        // 根据文件扩展名选择不同的文本提取方法
        if (lowerFileName.endsWith(".pdf")) {
            // PDF文件处理
            return extractTextFromPdf(file);
        } else if (lowerFileName.endsWith(".docx")) {
            // Word文档(.docx)处理
            return extractTextFromWord(file);
        } else if (lowerFileName.endsWith(".doc")) {
            // 不支持.doc格式
            throw new IOException("不支持.doc格式，请转换为.docx格式");
        } else if (lowerFileName.endsWith(".txt")) {
            // 文本文件直接读取，使用UTF-8编码
            return new String(file.getBytes(), "UTF-8");
        } else {
            // 不支持的文件格式
            throw new IOException("不支持的文件格式: " + contentType);
        }
    }

    /**
     * 从PDF文件中提取文本内容
     * 使用Apache PDFBox库解析PDF文件
     * @param file PDF文件对象
     * @return 提取的文本内容
     * @throws IOException PDF文件读取异常
     */
    private String extractTextFromPdf(MultipartFile file) throws IOException {
        try {
            // 使用Apache PDFBox库加载并解析PDF文件
            PDDocument document = PDDocument.load(file.getInputStream());
            
            // 创建PDFTextStripper对象用于提取文本
            PDFTextStripper stripper = new PDFTextStripper();
            
            // 提取文档中的所有文本
            String text = stripper.getText(document);
            
            // 关闭文档，释放资源
            document.close();
            
            return text;
        } catch (Exception e) {
            // 将异常包装为IOException并添加详细错误信息
            throw new IOException("PDF文件解析失败: " + e.getMessage());
        }
    }

    /**
     * 从Word文档(.docx格式)中提取文本内容
     * 使用Apache POI库解析Word文档
     * @param file Word文档对象
     * @return 提取的文本内容
     * @throws IOException Word文档读取异常
     */
    private String extractTextFromWord(MultipartFile file) throws IOException {
        try {
            String fileName = file.getOriginalFilename();
            if (fileName != null && fileName.toLowerCase().endsWith(".docx")) {
                // 使用try-with-resources语句确保文档资源正确关闭
                try (XWPFDocument document = new XWPFDocument(file.getInputStream())) {
                    // 创建字符串构建器存储提取的文本
                    StringBuilder textBuilder = new StringBuilder();
                    // 遍历文档中的所有段落
                    for (XWPFParagraph paragraph : document.getParagraphs()) {
                        // 将段落文本添加到构建器中并添加换行符
                        textBuilder.append(paragraph.getText()).append("\n");
                    }
                    // 返回提取的完整文本
                    return textBuilder.toString();
                }
            } else {
                throw new IOException("不支持的Word格式，请使用.docx格式");
            }
        } catch (Exception e) {
            // 将异常包装为IOException并添加详细错误信息
            throw new IOException("Word文件解析失败: " + e.getMessage());
        }
    }

    /**
     * 构建用于AI模型解析简历的提示词
     * 定义了需要从简历中提取的具体信息类型和格式要求
     * @param resumeText 简历文本内容
     * @return 构建好的提示词字符串
     */
    private String buildPrompt(String resumeText) {
        // 构建提示词，指导AI如何解析简历并返回结构化数据
        return "请根据以下简历内容，生成一个结构化的JSON格式数据。请严格按照指定的JSON结构返回，确保所有字段都有值（可以为空字符串或空数组）：\n" +
                "\n" +
                "简历内容：\n" + resumeText + "\n\n" +
                "请返回以下JSON结构：\n" +
                "{\n" +
                "  \"name\": \"候选人姓名\",\n" +
                "  \"email\": \"邮箱地址\",\n" +
                "  \"phone\": \"电话号码\",\n" +
                "  \"address\": \"地址\",\n" +
                "  \"age\": \"年龄\",\n" +
                "  \"birthDate\": \"出生日期\",\n" +
                "  \"nationality\": \"国籍\",\n" +
                "  \"linkedin\": \"LinkedIn链接\",\n" +
                "  \"github\": \"GitHub链接\",\n" +
                "  \"website\": \"个人网站\",\n" +
                "  \"summary\": \"个人简介\",\n" +
                "  \"objective\": \"求职目标\",\n" +
                "  \"skills\": [\"技能1\", \"技能2\", \"技能3\"],\n" +
                "  \"languages\": [\"语言1\", \"语言2\"],\n" +
                "  \"certifications\": [\"证书1\", \"证书2\"],\n" +
                "  \"projects\": [\"项目1\", \"项目2\"],\n" +
                "  \"education\": [\n" +
                "    {\n" +
                "      \"school\": \"学校名称\",\n" +
                "      \"degree\": \"学位\",\n" +
                "      \"major\": \"专业\",\n" +
                "      \"startDate\": \"开始时间\",\n" +
                "      \"endDate\": \"结束时间\",\n" +
                "      \"gpa\": \"GPA\",\n" +
                "      \"description\": \"描述\"\n" +
                "    }\n" +
                "  ],\n" +
                "  \"workExperience\": [\n" +
                "    {\n" +
                "      \"company\": \"公司名称\",\n" +
                "      \"position\": \"职位\",\n" +
                "      \"startDate\": \"开始时间\",\n" +
                "      \"endDate\": \"结束时间\",\n" +
                "      \"description\": \"工作描述\",\n" +
                "      \"location\": \"工作地点\",\n" +
                "      \"department\": \"部门\",\n" +
                "      \"achievements\": [\"成就1\", \"成就2\"]\n" +
                "    }\n" +
                "  ]\n" +
                "}\n\n" +
                "注意：\n" +
                "1. 请确保返回的是有效的JSON格式\n" +
                "2. 所有字段都必须包含，可以为空字符串或空数组\n" +
                "3. 日期格式建议使用YYYY-MM或YYYY-MM-DD\n" +
                "4. 技能、语言等数组字段请根据内容合理填充\n" +
                "5. 如果某些信息无法确定，请使用空字符串或空数组";
    }

    /**
     * 调用AI大语言模型进行简历内容解析
     * @param prompt 构建好的提示词
     * @param config AI模型配置信息
     * @return AI模型返回的响应内容
     */
    private String callLLM(String prompt, AIModelConfig config) {
        try {
            // 构建请求体，设置模型参数
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("model", config.getModelName());
            
            // 创建用户消息对象，包含角色和内容
            Map<String, String> message = new HashMap<>();
            message.put("role", "user");
            message.put("content", prompt);
            requestBody.put("messages", Collections.singletonList(message));
            
            // 设置生成参数：最大令牌数和温度值
            requestBody.put("max_tokens", 4000);  // 设置最大令牌数为4000，确保能处理较长的简历
            requestBody.put("temperature", 0.3);   // 设置较低的温度值，保证输出的确定性
            
            // 设置请求头，包括内容类型和认证信息
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            headers.set("Authorization", "Bearer " + config.getApiKey());
            
            // 创建HTTP请求实体，包含请求体和请求头
            HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
            
            // 发送POST请求调用AI模型API
            ResponseEntity<Map> response = restTemplate.postForEntity(config.getApiUrl(), request, Map.class);
            
            // 处理响应结果
            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
                Map<String, Object> responseBody = response.getBody();
                // 提取响应中的choices数组
                List<Map<String, Object>> choices = (List<Map<String, Object>>) responseBody.get("choices");
                if (choices != null && !choices.isEmpty()) {
                    // 获取第一条消息
                    Map<String, Object> messageObj = (Map<String, Object>) choices.get(0).get("message");
                    if (messageObj != null && messageObj.get("content") != null) {
                        // 返回AI生成的消息内容
                        return messageObj.get("content").toString();
                    }
                }
            }
            
            // 响应格式不正确时抛出异常
            throw new RuntimeException("API响应格式不正确");
            
        } catch (Exception e) {
            // 捕获并包装所有异常，添加详细错误信息
            throw new RuntimeException("AI模型调用失败: " + e.getMessage(), e);
        }
    }

    /**
     * 解析AI模型返回的响应，提取结构化的简历数据
     * 支持从混合文本中提取JSON部分并进行清理和解析
     * @param response AI模型返回的原始响应内容
     * @return 解析后的结构化简历数据Map
     * @throws IOException 解析过程中发生的IO异常
     */
    private Map<String, Object> parseResponse(String response) throws IOException {
        try {
            // 第一步：清理响应内容，提取JSON部分
            String jsonContent = response.trim();
            
            // 第二步：查找JSON开始标记，优先查找对象标记{，如果找不到则查找数组标记[
            int jsonStart = jsonContent.indexOf("{");
            if (jsonStart == -1) {
                jsonStart = jsonContent.indexOf("[");
            }
            
            // 第三步：如果找到JSON开始标记，则提取从该标记开始的所有内容
            if (jsonStart != -1) {
                jsonContent = jsonContent.substring(jsonStart);
            }
            
            // 第四步：移除Markdown代码块标记，处理AI模型可能返回的格式化内容
            jsonContent = jsonContent.replaceAll("```json", "");
            jsonContent = jsonContent.replaceAll("```", "");
            jsonContent = jsonContent.trim();
            
            // 第五步：尝试解析JSON内容为JsonNode对象
            JsonNode rootNode = objectMapper.readTree(jsonContent);
            Map<String, Object> result = new HashMap<>();
            
            // 第六步：提取基本信息字段（字符串类型）
            result.put("name", getStringValue(rootNode, "name"));
            result.put("email", getStringValue(rootNode, "email"));
            result.put("phone", getStringValue(rootNode, "phone"));
            result.put("address", getStringValue(rootNode, "address"));
            result.put("age", getStringValue(rootNode, "age"));
            result.put("birthDate", getStringValue(rootNode, "birthDate"));
            result.put("nationality", getStringValue(rootNode, "nationality"));
            result.put("linkedin", getStringValue(rootNode, "linkedin"));
            result.put("github", getStringValue(rootNode, "github"));
            result.put("website", getStringValue(rootNode, "website"));
            result.put("summary", getStringValue(rootNode, "summary"));
            result.put("objective", getStringValue(rootNode, "objective"));
            
            // 第七步：处理字符串数组字段：技能、语言、证书和项目
            result.put("skills", getStringListValue(rootNode, "skills"));
            result.put("languages", getStringListValue(rootNode, "languages"));
            result.put("certifications", getStringListValue(rootNode, "certifications"));
            result.put("projects", getStringListValue(rootNode, "projects"));
            
            // 第八步：处理复杂对象数组字段：教育经历和工作经验
            result.put("education", getObjectListValue(rootNode, "education"));
            result.put("workExperience", getObjectListValue(rootNode, "workExperience"));
            
            // 返回结构化的简历数据Map
            return result;
            
        } catch (Exception e) {
            // 将解析过程中的异常包装为IOException并添加详细错误信息
            throw new IOException("JSON解析失败: " + e.getMessage(), e);
        }
    }

    /**
     * 获取当前支持的所有AI模型列表
     * @return 模型名称列表
     */
    public List<String> getSupportedModels() {
        return new ArrayList<>(modelConfigs.keySet());
    }

    /**
     * 从JsonNode中安全地获取字符串字段值
     * 如果字段不存在或为空，返回空字符串（避免空指针异常）
     * @param node JsonNode对象
     * @param fieldName 字段名
     * @return 字符串字段值或空字符串
     */
    private String getStringValue(JsonNode node, String fieldName) {
        JsonNode fieldNode = node.get(fieldName);
        return fieldNode != null && !fieldNode.isNull() ? fieldNode.asText() : "";
    }

    /**
     * 从JsonNode中安全地获取字符串数组字段值
     * 如果字段不存在、为空或不是数组，返回空列表（避免空指针异常）
     * @param node JsonNode对象
     * @param fieldName 字段名
     * @return 字符串列表或空列表
     */
    @SuppressWarnings("unchecked")
    private List<String> getStringListValue(JsonNode node, String fieldName) {
        JsonNode arrayNode = node.get(fieldName);
        List<String> result = new ArrayList<>();
        
        // 仅当节点存在且是数组类型时才处理
        if (arrayNode != null && arrayNode.isArray()) {
            for (JsonNode item : arrayNode) {
                if (item.isTextual()) {
                    result.add(item.asText());
                } else {
                    // 对于非文本类型的元素，转换为字符串
                    result.add(item.toString());
                }
            }
        }
        
        return result;
    }

    /**
     * 从JsonNode中安全地获取对象数组字段值
     * 每个对象转换为Map<String, Object>格式，同时处理嵌套数组结构
     * @param node JsonNode对象
     * @param fieldName 字段名
     * @return Map对象列表或空列表
     */
    @SuppressWarnings("unchecked")
    private List<Map<String, Object>> getObjectListValue(JsonNode node, String fieldName) {
        JsonNode arrayNode = node.get(fieldName);
        List<Map<String, Object>> result = new ArrayList<>();

        // 仅当节点存在且是数组类型时才处理
        if (arrayNode != null && arrayNode.isArray()) {
            for (JsonNode item : arrayNode) {
                if (item.isObject()) {
                    // 创建Map存储对象字段
                    Map<String, Object> map = new HashMap<>();
                    
                    // 遍历对象的所有字段
                    item.fields().forEachRemaining(entry -> {
                        JsonNode valueNode = entry.getValue();
                        if (valueNode.isTextual()) {
                            // 文本类型字段直接添加
                            map.put(entry.getKey(), valueNode.asText());
                        } else if (valueNode.isArray()) {
                            // 数组类型字段需要特殊处理
                            List<String> list = new ArrayList<>();
                            for (JsonNode arrayItem : valueNode) {
                                if (arrayItem.isTextual()) {
                                    list.add(arrayItem.asText());
                                } else {
                                    list.add(arrayItem.toString());
                                }
                            }
                            map.put(entry.getKey(), list);
                        } else {
                            // 其他类型字段转换为字符串
                            map.put(entry.getKey(), valueNode.asText());
                        }
                    });
                    result.add(map);
                }
            }
        }
        
        return result;
    }

    /**
     * 验证API密钥是否有效
     * 检查密钥是否为空、空白或长度过短（少于10个字符）
     * @param apiKey 待验证的API密钥
     * @return 如果API密钥无效返回true，否则返回false
     */
    private boolean isInvalidApiKey(String apiKey) {
        return apiKey == null || apiKey.trim().isEmpty() || apiKey.trim().length() < 10;
    }

    /**
     * AI模型配置内部类
     * 存储调用AI模型所需的配置信息
     * 包含API URL、API密钥和模型名称
     */
    private static class AIModelConfig {
        private final String apiUrl;  // AI模型API的URL地址
        private final String apiKey;  // API密钥
        private final String modelName;  // 模型名称

        /**
         * 构造函数，初始化AI模型配置
         * @param apiUrl API的URL地址
         * @param apiKey API密钥
         * @param modelName 模型名称
         */
        public AIModelConfig(String apiUrl, String apiKey, String modelName) {
            this.apiUrl = apiUrl;
            this.apiKey = apiKey;
            this.modelName = modelName;
        }

        /**
         * 获取API的URL地址
         * @return API URL
         */
        public String getApiUrl() {
            return apiUrl;
        }

        /**
         * 获取API密钥
         * @return API密钥
         */
        public String getApiKey() {
            return apiKey;
        }

        /**
         * 获取模型名称
         * @return 模型名称
         */
        public String getModelName() {
            return modelName;
        }
    }
}